﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Drawing;
using System.Diagnostics.Contracts;

namespace RPGMapRevealer
{
    public class MapUpdatedEventArgs : EventArgs
    {
        public MapInfo Map { get; set; }
    }

    /// <summary>
    /// Represents the information about a Map
    /// </summary>
    public class MapInfo
    {
        public event EventHandler<MapUpdatedEventArgs> MapUpdated;

        private TileCollection _tiles = new TileCollection();
        /// <summary>
        /// The tiles in this Map
        /// </summary>
        public TileCollection Tiles
        {
            get
            {
                Contract.Ensures(Contract.Result<TileCollection>() != null);
                return _tiles;
            }
        }

        /// <summary>
        /// Finds a tile with the matching x and y coordinate
        /// </summary>
        /// <param name="x"></param>
        /// <param name="y"></param>
        /// <returns></returns>
        public Tile GetTileByXY(int x, int y)
        {
            foreach (Tile tile in _tiles)
            {
                if (tile == null) { continue; }
                if (tile.X == x && tile.Y == y)
                {
                    return tile;
                }
            }
            return null;
        }

        private GridConfig _gridConfig = new GridConfig();
        /// <summary>
        /// The size, offset, and border of the tile
        /// </summary>
        public GridConfig GridConfig
        {
            get
            {
                Contract.Ensures(Contract.Result<GridConfig>() != null);
                return _gridConfig;
            }
            set
            {
                Contract.Requires(value != null);
                _gridConfig = value;
            }
        }

        /// <summary>
        /// The public image of the Map
        /// </summary>
        public Image PublicImage
        { get; set; }
        /// <summary>
        /// The secret image of the Map (secret doors, traps, etc)
        /// </summary>
        public Image SecretImage
        { get; set; }

        /// <summary>
        /// The folder where the tiles are located
        /// </summary>
        public DirectoryInfo TileFolder
        { get; set; }

        [ContractInvariantMethod]
        private void InvariantMethod()
        {
            Contract.Invariant(_tiles != null);
            Contract.Invariant(_playerPositions != null);
            Contract.Invariant(_gridConfig != null);
        }

        private Dictionary<int, Tile> _playerPositions = new Dictionary<int, Tile>();
        /// <summary>
        /// Moves a player token and any light source at the players current location, to a new location
        /// </summary>
        /// <param name="player">The player to be moved</param>
        /// <param name="destination">The tile to move the player to</param>
        public void MovePlayer(int player, Tile destination)
        {
            Contract.Requires(destination != null);
            int lightSource = 0;
            Tile old = null;
            if (_playerPositions.TryGetValue(player, out old) && old != null)
            {
                bool stillPresent = false;
                foreach (int p in _playerPositions.Keys)
                {
                    if (_playerPositions[p] == old && p != player)
                    { stillPresent = true; }
                }
                old.IsPlayerPresent = stillPresent;
                lightSource = old.LightSource;
                old.LightSource = 0;
            }
            _playerPositions[player] = destination;
            destination.IsPlayerPresent = true;
            destination.LightSource = Math.Max(lightSource, destination.LightSource);
        }
        /// <summary>
        /// Returns the Tile that the current player is on, or null if the player has not been placed
        /// </summary>
        /// <param name="player">A player to look for</param>
        /// <returns>the Tile that the current player is on, or null if the player has not been placed</returns>
        public Tile PositionOfPlayer(int player)
        {
            Tile result = null;
            _playerPositions.TryGetValue(player, out result);
            return result;
        }
        /// <summary>
        /// The tiles containing players
        /// </summary>
        /// <returns>The tiles containing players</returns>
        public IEnumerable<Tile> PositionOfPlayers()
        {
            return _playerPositions.Values;
        }
        /// <summary>
        /// All the players at a certain position
        /// </summary>
        /// <param name="position">The tile to search</param>
        /// <returns>All the players at a certain position</returns>
        public IEnumerable<int> PlayersAtPosition(Tile position)
        {
            foreach (int player in _playerPositions.Keys)
            {
                if (_playerPositions[player] == position)
                { yield return player; }
            }
        }

        /// <summary>
        /// Recalculates the light and line of sight based on the position of the players and any available light sources
        /// </summary>
        public void RecalculateLightAndLineOfSight(bool suppressUpdate = false)
        {
            List<Tile> players = new List<Tile>(PositionOfPlayers());
            List<Tile> lights = new List<Tile>();
            List<Tile> nearbyTiles = new List<Tile>();
            foreach (Tile player in players)
            {
                nearbyTiles.AddRange(player.Search().Flatten());
            }

            foreach (Tile t in Tiles)
            {
                if (t == null) { continue; }
                if (t.LightLevel > 2)
                {
                    t.LightLevel = 2;
                }
                if (t.LightSource > 0 && nearbyTiles.Contains(t))
                {
                    lights.Add(t);
                }
                t.SupressLight = !nearbyTiles.Contains(t);                
            }

            List<Tile> inPlayerLOS = new List<Tile>();
            
            foreach (Tile player in players)
            {
                if (player == null) { continue; }
                inPlayerLOS.AddRange(player.CalculateLineOfSight(nearbyTiles));
            }

            foreach (Tile lightSource in lights)
            {
                if (lightSource == null) { continue; }
                int power = lightSource.LightSource;
                if (power <= 0) { power = 1; }
                IEnumerable<IList<Tile>> tilesBeingLit = lightSource.Search(power);
                IList<Tile> tilesInLoS = lightSource.CalculateLineOfSight(tilesBeingLit.Flatten()).ToList();
                foreach (IEnumerable<Tile> step in tilesBeingLit)
                {
                    if (step == null) { continue; }
                    foreach (Tile nearby in step)
                    {
                        if (nearby == null) { continue; }
                        if (tilesInLoS.Contains(nearby) && inPlayerLOS.Contains(nearby))
                        {
                            nearby.LightLevel = Math.Max(nearby.LightLevel, power);
                        }
                    }
                    power--;
                }
            }
            HashSet<Tile> adjacentToLight = new HashSet<Tile>();
            foreach (Tile tile in Tiles)
            {
                if (tile.LightLevel > 2)
                {
                    foreach (Tile adjacent in tile.AdjacentTiles)
                    {
                        if (!tile.NoLOSBetweenTiles.Contains(adjacent))
                        { adjacentToLight.Add(adjacent); }
                    }
                }
            }
            foreach (Tile tile in adjacentToLight)
            {
                if (tile.LightLevel < 1)
                {
                    tile.LightLevel = 1;
                }
            }

            if (MapUpdated != null && !suppressUpdate)
            {
                try { MapUpdated(this, new MapUpdatedEventArgs() { Map = this }); }
                catch (Exception) { }
            }
        }
    }
}
